#include <ucontext.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "list.h"
#include "thread.h"
#include <valgrind/valgrind.h>

struct thread_elt *dead_begin=NULL;
struct thread_elt *ready_begin=NULL; //file
struct thread_elt *cursor_ready=NULL; //unique element

//crée la structure thread_elt pour le main et initialise les listes lors de son premier appel
static void thread_init()
{
   if(cursor_ready==NULL)
    {
      struct thread_elt * elt=malloc(sizeof(*elt));
      elt->waiting_begin=NULL;
      elt->next=NULL;
      elt->retval=NULL;
      elt->dead=0;
      ready_begin=elt;
      cursor_ready=elt;
      
    }
}

//libère le thread
static void thread_free(thread_t t){
  VALGRIND_STACK_DEREGISTER(t->valgrind_stackid);
  free(t->context.uc_stack.ss_sp);
  if(t->retval!=NULL)
    free(t->retval);
  free(t);
}


int thread_create(thread_t *newthread, void *(*func)(void *), void *funcarg){
 
  thread_init(); 
  struct thread_elt *t;

  //création du context
  t=malloc(sizeof(*t));
  getcontext(&(t->context));
  t->context.uc_stack.ss_size = 64*1024;
  t->context.uc_stack.ss_sp = malloc(t->context.uc_stack.ss_size);
  t->valgrind_stackid = VALGRIND_STACK_REGISTER(t->context.uc_stack.ss_sp,
						 t->context.uc_stack.ss_sp + t->context.uc_stack.ss_size);
  t->context.uc_link = &cursor_ready->context;
  makecontext(&(t->context),(void(*)(void))func,1,funcarg);

  //initialisation des champs
  t->waiting_begin=NULL;
  t->next=NULL;
  t->retval=NULL;
  t->dead=0;
  *newthread = t;

  add_list(*newthread,&ready_begin);
  thread_yield();
  return 0;
}

thread_t thread_self(void){  
  thread_init();
  return cursor_ready;
}


int thread_yield(void){
  //appel de yield avant création du premier thread
  if(cursor_ready == NULL)
    return 0;
  
  struct thread_elt * t = cursor_ready;
 
  //mise à jour de cursor_ready
  if(cursor_ready->next != NULL)
    cursor_ready = cursor_ready->next;
  else {
    cursor_ready = ready_begin;
  }

  swapcontext(&(t->context), &(cursor_ready->context)); 
  return 1;
}



int thread_join(thread_t thread, void **retval){
  struct thread_elt *next_elt, * t = cursor_ready;
  
  if(cursor_ready == NULL)
    return 0;

  if(thread->dead) //thread mort
    {
      if(retval!=NULL)
	*retval = *(thread->retval);
      remove_list(thread, &dead_begin);
      thread_free(thread);
      thread_yield();
      return 0;
    }
  else //thread viviant
    {
      if(cursor_ready->next != NULL)
	next_elt = cursor_ready->next;
      else 
	next_elt = ready_begin;
      
      remove_list(cursor_ready, &ready_begin);
      cursor_ready = next_elt;

      add_list(t, &(thread->waiting_begin));
      t->next = NULL;
      t->retval = retval;

      swapcontext(&(t->context), &(cursor_ready->context));
      thread_free(thread);
      return 0;
      
    }
}


void thread_exit(void *retval) {
  struct thread_elt *tmp = cursor_ready->waiting_begin;
  struct thread_elt *former_crs = cursor_ready;
  
  cursor_ready->dead = 1;

  if(tmp != NULL) { //thread attendu

    while(tmp != NULL) {
      //passage de la valeur de retour aux threads attendants
      if(tmp->retval!=NULL)
	*(tmp->retval) = retval;
      tmp = tmp->next;
    }

    add_list(cursor_ready->waiting_begin, &ready_begin);
    cursor_ready->waiting_begin = NULL;
    
    if(cursor_ready->next != NULL)
      cursor_ready = cursor_ready->next;
    else 
      cursor_ready = ready_begin;
    
    remove_list(former_crs, &ready_begin);
    setcontext(&(cursor_ready->context));
  }
  else //thread non attendu
    {

      if(cursor_ready->next != NULL)
	cursor_ready = cursor_ready->next;
      else 
	cursor_ready = ready_begin;

      remove_list(former_crs, &ready_begin);
      add_list(former_crs, &dead_begin);
      
      //stockage de la valeur de retour
      former_crs->retval=malloc(sizeof(void *));
      *(former_crs->retval) = retval;
     
      
      setcontext(&(cursor_ready->context));
    }
}









